class Vue {
    constructor(options) {
        this.$data = options.data
        //调用数据劫持的方法
        Observe(this.$data)

        //属性代理,不用每次都vm.$data
        Object.keys(this.$data).forEach((key) => {
            Object.defineProperty(this,key,{
                enumerable: true,
                configurable: true,
                get() {
                    return this.$data[key]
                },
                set(newValue) {
                    this.$data[key] = newValue
                }
            })
        })

        // 调用模板编译的函数
        Compile(options.el,this)
    }
}

//定义一个数据劫持的方法
function Observe(obj) {
    // 递归终止条件
    if(!obj || typeof obj !== "object") return
    const dep = new Dep();

    // 通过Object.keys(obj) 获知到当前obj上的每个属性
    Object.keys(obj).forEach((key) => {
        let value = obj[key]
        //将value当做子节点进行递归
        Observe(value)

        //为当前key对应的属性，添加getter，setter
        Object.defineProperty(obj,key,{
            enumerable: true,
            configurable:true,

            get(){
                console.log(`有人获取了${key}的值`);
                //执行下面这一行，刚才new的Watcher实例就被放入dep.subs中了
                Dep.target &&　dep.addSub(Dep.target)
                return value
            },
            set(newValue){
                value = newValue
                //为新赋值的对象也加上getter和setter
                Observe(value)
                dep.notify();
            }
        })
    })
}

// 对HTML结构进行模板编译的方法
function Compile(el,vm) {
    //获知el对应的DOM元素
   vm.$el =  document.querySelector(el)

    // 创建文档碎片，提高DOM操作的性能（在内存里修改dom元素，而不是在页面上修改dom元素，这样的不会引起重绘）
    const fragment = document.createDocumentFragment()

    while((childNode = vm.$el.firstChild)){
       fragment.appendChild(childNode)
    }
    console.log(fragment)
    replace(fragment)

    function replace(node) {
       // 匹配两个大括号
        const regMustache = /\{\{s*(\S+)\s*\}\}/

        //证明当前node节点是一个文本节点，需要正则替换
        if(node.nodeType === 3) {
            console.log("+++++++++++++++");
            //文本子节点，也是一个dom对象，如果要获取文本子节点的字符串内容，需要调用textContent属性获取
            const text = node.textContent
            console.log(text);
            //进行字符串的正则匹配与提取
            const execResult = regMustache.exec(text)
            console.log(execResult);
            if(execResult) {
                const value = execResult[1].split(".").reduce((newObj,k) => newObj[k],vm) //这一步不理解，详见reduce.js 如何链式获取属性
                console.log(value);
                node.textContent = text.replace(regMustache,value) //核心：这里用的是javascript的replace方法不是前面设置的replace方法，把{{}}里的东西，替换成newValue

                //这个时候，创建Watcher类的实例
                new Watcher(vm,execResult[1],(newValue) => {
                    node.textContent = text.replace(regMustache,newValue)
                })
            }
            return
        }
        //证明当前node节点不是一个文本节点，可能是一个dom节点，必须继续递归
        node.childNodes.forEach((child) => replace(child))
    }

    //进行模板编译
    vm.$el.appendChild(fragment)
}

class Dep {
    constructor() {
        this.subs = []
    }

    addSub(watcher) {
        this.subs.push(watcher)
    }

    notify() {
        this.subs.forEach(watcher => watcher.update())
    }
}

class Watcher {
    constructor(vm,key,cb) {
        this.vm = vm
        this.key = key
        this.cb = cb

        // 下面三行代码，负责把创建的Watcher实例存到Dep实例的subs数组中
        Dep.target = this
        key.split('.').reduce((newObj,k) => newObj[k],vm)  //从vm上取值，会触发get函数
        Dep.target = null
    }

    //触发Watcher中负责更新的回调函数cb
    update() {
        const value = this.key.split(".").reduce((newObj,k) => newObj[k],this.vm)
        this.cb(value)
    }
}
